﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Lucene.Linq.Expressions;
using Lucene.Linq.Mapping;
using Lucene.Linq.Search;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;

namespace Lucene.Linq
{
    /// <summary>
    /// Extension methods
    /// </summary>
    public static class Extensions
    {
        #region String

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="term"></param>
        /// <returns></returns>
        public static bool Like(this string source, string term)
        {
            return Like(source, term, 0.5);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="term"></param>
        /// <param name="similarity"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Like(this string source, string term, double similarity)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");

            var directory = new RAMDirectory();

            var writer = new IndexWriter(directory, new StandardAnalyzer(), true);
            var document = new Document();
            document.Add(new Field("temp", source, Field.Store.YES, Field.Index.TOKENIZED));
            writer.AddDocument(document);
            writer.Optimize();


            var searcher = new IndexSearcher(directory);
            Query query = new TermQuery(new Term("temp", "m*"));
            Hits hits = searcher.Search(query);

            writer.Close();
            directory.Close();
            return hits.Length() > 0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="phrase"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Match(this string source, string phrase)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="terms"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Match(this string source, params string[] terms)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="factor"></param>
        /// <returns></returns>
        public static string Boost(this string source, int factor)
        {
            return source + "^" + factor.ToString(CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string RequireEach(this string source)
        {
            var phrase = new StringBuilder();
            string[] terms = source.Split(' ');

            foreach (string term in terms)
            {
                phrase.Append("+").Append(term).Append(" ");
            }

            return phrase.ToString().Trim();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string Require(this string source)
        {
            var phrase = new StringBuilder();
            bool multiTerms = source.Split(' ').Length > 1;

            if (multiTerms)
            {
                phrase.Append("+(");
                phrase.Append(source);
                phrase.Append(")");
            }
            else
            {
                phrase.Append("+");
                phrase.Append(source);
            }

            return phrase.ToString().Trim();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="startRange"></param>
        /// <param name="endRange"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Between(this string source, string startRange, string endRange)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="startRange"></param>
        /// <param name="endRange"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Include(this string source, string startRange, string endRange)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        #endregion

        #region IIndex

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="term"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Like(this IIndexable source, string term)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="term"></param>
        /// <param name="similarity"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Like(this IIndexable source, string term, double similarity)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="phrase"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Match(this IIndexable source, string phrase)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="terms"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Match(this IIndexable source, params string[] terms)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="phrase"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool StartsWith(this IIndexable source, string phrase)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="startRange"></param>
        /// <param name="endRange"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Between(this IIndexable source, string startRange, string endRange)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="startRange"></param>
        /// <param name="endRange"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Include(this IIndexable source, string startRange, string endRange)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="queryString"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        public static bool Search(this IIndexable source, string queryString)
        {
            throw new InvalidOperationException("Operation is reserved for Lucene Query Expression's only");
        }

        #endregion

        #region Query<T>

        #region IEnumerable<T> Search<T>

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="queryString"></param>
        /// <param name="defaultFieldName"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static IEnumerable<T> Search<T>(this Query<T> source, string queryString, string defaultFieldName)
        {
            string[] defaultFieldNames = { defaultFieldName };
            return source.Search(queryString, defaultFieldNames);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="queryString"></param>
        /// <param name="defaultFieldNames"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public static IEnumerable<T> Search<T>(this Query<T> source, string queryString, string[] defaultFieldNames)
        {
            throw new NotImplementedException();
            /*
            ParsingQueryTranslator translator = new ParsingQueryTranslator();
            Query query = translator.Translate(queryString, defaultFieldNames);

            IndexSearcher searcher = source.Context.Searcher;
            Hits hits = searcher.Search(query);

            Type elementType = TypeSystem.GetElementType(typeof(T));

            return Activator.CreateInstance(
              typeof(ObjectReader<>).MakeGenericType(elementType),
              BindingFlags.Instance | BindingFlags.NonPublic,
              null,
              new object[] { hits },
              null
              ) as IEnumerable<T>;
             */
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="queryString"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static IEnumerable<T> Search<T>(this Query<T> source, string queryString)
        {
            string[] defaultFieldNames = (from property in typeof(T).GetProperties()
                                          from fieldAttribute in
                                              property.GetCustomAttributes(typeof(FieldAttribute), false)
                                          select new { Field = fieldAttribute as FieldAttribute, Property = property }
                                         )
                .Where(t => t.Field.IsDefault)
                .Select(t => t.Field.Name ?? t.Property.Name)
                .ToArray();

            return source.Search(queryString, defaultFieldNames);
        }

        #endregion

        #endregion

        #region Tokenization and Analyis

        /// <summary>
        /// 
        /// </summary>
        /// <param name="humanInput"></param>
        /// <param name="fieldName"></param>
        /// <param name="a"></param>
        /// <returns></returns>
        public static string[] Tokenize(this string humanInput, string fieldName, Analyzer a)
        {
            string filteredText = humanInput;
            //(filter) ? humanInput.Replace('&', ' ').Replace(',', ' ').Trim().ToLower() : humanInput;

            var terms = new List<string>();
            using (var sr = new StringReader(filteredText))
            {
                TokenStream ts = a.TokenStream(fieldName, sr);
                Token inputtoken = ts.Next();
                while (inputtoken != null)
                {
                    string termText = inputtoken.TermText();
                    terms.Add(termText);
                    inputtoken = ts.Next();
                }
            }

            return terms.ToArray();
        }

        #endregion
    }
}